package com.tianji.learning.controller.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.LearningConstants;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.controller.service.IPointsBoardService;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.query.PointsBoardQuery;
import com.tianji.learning.domain.vo.PointsBoardItemVO;
import com.tianji.learning.domain.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author zt
 * @since 2024-09-16
 */
@RequiredArgsConstructor
@Service
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {
    private final UserClient userClient;
    private final RedisTemplate redisTemplate;

    /**
     * 查询排行榜，需要查询的是当前用户的排名和积分信息
     * 还有分页查询排行榜的信息
     * @param query
     * @return
     */
    @Override
    public PointsBoardVO queryPointsBoardList(PointsBoardQuery query) {
        //1、获取用户id
        Long userId = UserContext.getUser();
        //2、判断是当前赛季还是历史赛季
        //query.season是0或者null表示是当前赛季(null是默认值，表示当前赛季)
        boolean isCurrentSeason = query.getSeason() == 0 || query.getSeason() == null;
        //创建排行榜对象，用户保存当前用户的积分排行信息
        PointsBoard board = new PointsBoard();
        //创建排行榜列表，存储分页查询的排行榜数据
        List<PointsBoard> result = new ArrayList<>();
        if(isCurrentSeason){
            //3、是当前赛季，当前赛季的信息在redis中的ZSet集合中存放，所以需要操作redis获取
            /*
            这里获取当前赛季的排行榜会使用到redis中的ZSet集合
            这里对ZSet做一个总结：
            在Redis中，有序集合（sorted set，也称为zset）是一种特殊的数据结构
            它结合了集合（set）和有序列表的特性。以下是有序集合的一些关键特性和操作：
            1、zset也是集合的一种，所以它不允许重复，这是集合的基本特性
            2、zset是有序集合，有序是通过将set中每一个元素都关联上一个分数score，通过这个score来进行排序，score是浮点数
            3、zset会自动排序，默认是分数小的排在前面，分数大的排在后面，即升序排列
            基于这些特点，zset特别适合用于实现排行榜，score就是积分值，而且可以自动排序
             */
            //3.1 查询当前用户当前赛季的排名和积分信息
            //获取操作redis的key：前缀加年月，表示当前月的排行榜，用于获取该用户的排名信息
            LocalDateTime now = LocalDateTime.now();
            String format = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
            String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + format;
            //获取积分值，通过key表示当前赛季的zset集合，userId.toString()是zset的成员，score是该用户的分数
            Double score = redisTemplate.opsForZSet().score(key, userId.toString());
            //获取排名，按积分多少逆序排名，因为zset默认是从小到大排序的，从大到小排序是逆序，rank从0开始
            Long rank = redisTemplate.opsForZSet().reverseRank(key, userId.toString());
            //返回数据
            board.setPoints(score.intValue());
            board.setRank(rank.intValue());
            //3.2 查询当前赛季的积分排行榜的分页数据，还是通过redis中的zset获取
            //页大小
            Integer pageSize = query.getPageSize();
            //页号
            Integer pageNo = query.getPageNo();
            //根据页号和页大小计算该页的开始和结束的分页值
            int start = (pageNo - 1)*pageSize;
            int end = start + pageSize;
            //reverseRangeWithScores方法是获取指定排名范围的元素及分数，逆序排列，即由大到小排名
            //key表示操作哪个有序集合，start和end是排名范围
            //该方法返回一个 Set 集合，其中包含了指定排名范围内的所有元素及其对应的分数。
            //每个元素都是一个 ZSetOperations.TypedTuple 对象，该对象包含元素的值和对应的分数
            Set<ZSetOperations.TypedTuple> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
            if(CollUtils.isEmpty(typedTuples)){
                result = CollUtils.emptyList();
            }else{
                //排名是start+1，因为zset的rank是从0开始计数的
                int allRank = start + 1;
                for (ZSetOperations.TypedTuple typedTuple : typedTuples) {
                    //value中存储的是userId
                    String value = typedTuple.getValue().toString();
                    //获取分数score
                    score = typedTuple.getScore();
                    if(value==null||score==null){
                        continue;
                    }
                    board = new PointsBoard();
                    board.setRank(allRank++);
                    board.setUserId(Long.valueOf(value));
                    board.setPoints(score.intValue());
                    result.add(board);
                }
            }
        }else{
            //todo 历史赛季
        }
        //5、获取排行榜中的数据的用户信息
        //通过远程调用用户接口实现对用户信息的查询，首先就是要获得希望查询的用户id的集合
        //使用stream流的方式获取用户id集合
        Set<Long> userIds = result.stream().map(pointsBoard -> pointsBoard.getUserId()).collect(Collectors.toSet());
        //调用用户服务的feign接口
        List<UserDTO> userDTOS = userClient.queryUserByIds(userIds);
        if(CollUtils.isEmpty(userDTOS)){
            throw new BadRequestException("用户信息不存在");
        }
        //将用户信息转化为map，便于数据的使用
        Map<Long, UserDTO> userDTOMap = userDTOS.stream().collect(Collectors.toMap(UserDTO::getId, userDTO -> userDTO));
        //6、封装vo返回
        //创建vo对象
        PointsBoardVO vo = new PointsBoardVO();
        //封装当前用户的积分和排名信息
        vo.setPoints(board.getPoints());
        vo.setRank(board.getRank());
        //创建分页排行榜信息列表对象
        List<PointsBoardItemVO> voList = new ArrayList<>();
        for (PointsBoard pointsBoard : result) {
            PointsBoardItemVO itemVO = new PointsBoardItemVO();
            itemVO.setPoints(pointsBoard.getPoints());
            itemVO.setRank(pointsBoard.getRank());
            itemVO.setName(userDTOMap.get(pointsBoard.getUserId()).getUsername());
            voList.add(itemVO);
        }
        vo.setBoardList(voList);
        return vo;
    }


    /***
     * 查询当前赛季的排行榜列表
     * @param key
     * @param pageNo
     * @param pageSize
     * @return
     */
    @Override
    public List<PointsBoard> queryCurrentBoard(String key, Integer pageNo, Integer pageSize) {
        //1.计算start和end 分页值
        int start = (pageNo-1)*pageSize;
        int end = start +pageSize;
        //2.利用zreverange名 会按分数倒序 分页查询
        Set<ZSetOperations.TypedTuple> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
        if(CollUtils.isEmpty(typedTuples)){
            return CollUtils.emptyList();
        }
        int rank = start + 1;
        List<PointsBoard> result = new ArrayList<>();
        for (ZSetOperations.TypedTuple typedTuple : typedTuples) {
            String value = typedTuple.getValue().toString();//用户id
            Double score = typedTuple.getScore();//总积分值
            if(StringUtils.isBlank(value) || score==null){
                continue;
            }
            PointsBoard board = new PointsBoard();
            board.setUserId(Long.valueOf(value));//用户id
            board.setPoints(score.intValue());//积分
            board.setRank(rank++);//按排名顺序
            result.add(board);
        }
        return result;
    }

    /***
     * 查询历史赛季我的积分和排名
     * @param key
     * @param seasonId
     * @return
     */
    private PointsBoard queryMyHistoryBoard(String key, Long seasonId) {
        //todo
        return null;
    }
    //todo
    private List<PointsBoard> queryHistoryBoard(PointsBoardQuery query) {
        return null;
    }


    /***
     * 查询当前赛季我的积分和排名
     * @param key
     * @param seasonId
     * @return
     */
    public PointsBoard queryMyCurrentBoard(String key, Long seasonId) {
        //1.获取当前用户id
        Long userId = UserContext.getUser();
        //2.获取积分值
        Double score = redisTemplate.opsForZSet().score(key, userId.toString());
        //3.获取排名
        Long rank = redisTemplate.opsForZSet().reverseRank(key, userId.toString());//按积分多少逆序排名 积分越大rank越小 rank从0开始
        //4.返回数据
        PointsBoard board = new PointsBoard();
        board.setRank(rank==null?0:rank.intValue()+1);//从0开始，需要加1
        board.setPoints(score==null?0:score.intValue());
        return board;
    }
    @Override
    public void createPointsBoardTableBySeason(Integer id) {
        getBaseMapper().createPointsBoardTable(LearningConstants.POINTS_BOARD_TABLE_PREFIX + id);
    }
}
